package io.gitee.mingbaobaba.security.oauth2.starter.endpoint;

import io.gitee.mingbaobaba.security.core.annotion.SecurityIgnore;
import io.gitee.mingbaobaba.security.core.domain.SecurityUserDetails;
import io.gitee.mingbaobaba.security.core.exception.SecurityBaseException;
import io.gitee.mingbaobaba.security.core.factory.SecurityFactory;
import io.gitee.mingbaobaba.security.core.request.SecurityRequest;
import io.gitee.mingbaobaba.security.core.response.SecurityResponseWrapper;
import io.gitee.mingbaobaba.security.core.service.SecurityUserDetailsService;
import io.gitee.mingbaobaba.security.core.utils.SecurityUtil;
import io.gitee.mingbaobaba.security.oauth2.SecurityOauth2Manager;
import io.gitee.mingbaobaba.security.oauth2.constants.SecurityOauth2ApiConstant;
import io.gitee.mingbaobaba.security.oauth2.constants.SecurityOauth2CommonConstant;
import io.gitee.mingbaobaba.security.oauth2.constants.SecurityOauth2ErrorCodeConstant;
import io.gitee.mingbaobaba.security.oauth2.constants.SecurityOauth2ParamConstant;
import io.gitee.mingbaobaba.security.oauth2.domain.SecurityOauth2AccessToken;
import io.gitee.mingbaobaba.security.oauth2.domain.SecurityOauth2Details;
import io.gitee.mingbaobaba.security.oauth2.domain.SecurityOauth2RefreshToken;
import io.gitee.mingbaobaba.security.oauth2.domain.model.AuthorizeModel;
import io.gitee.mingbaobaba.security.oauth2.enums.GrantType;
import io.gitee.mingbaobaba.security.oauth2.exception.SecurityOauth2Exception;
import io.gitee.mingbaobaba.security.oauth2.domain.SecurityOauth2Client;
import io.gitee.mingbaobaba.security.oauth2.service.SecurityOauth2Service;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * <p>oauth2 api</p>
 *
 * @author yingsheng.ye
 * @version 1.0.0
 * @since 2023/9/9 22:02
 */
@RestController
@Slf4j
@SecurityIgnore
public class SecurityOauth2Endpoint {

    /**
     * 授权服务
     *
     * @return ModelAndView
     */
    @SneakyThrows
    @GetMapping(SecurityOauth2ApiConstant.AUTHORIZE_URL)
    public ModelAndView authorize(ModelAndView modelAndView) {
        SecurityOauth2Service securityOauth2Service = SecurityOauth2Manager.getSecurityOauth2Service();
        AuthorizeModel authorizeModel = securityOauth2Service.buildAuthorize();
        if (authorizeModel.isAuthorizeSuccess()) {
            String responseType = authorizeModel.getSecurityOauth2Client().getResponseType();
            //授权码模式（Authorization Code Grant）
            if (SecurityOauth2CommonConstant.RESPONSE_TYPE_CODE.equals(responseType)) {
                authorizationCodeGrant(securityOauth2Service, modelAndView, authorizeModel);
            }
            //隐式授权模式（Implicit Grant）
            else if (SecurityOauth2CommonConstant.RESPONSE_TYPE_TOKEN.equals(responseType)) {
                implicitGrant(modelAndView, authorizeModel);
            } else {
                throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_UNRECOGNIZED_RESPONSE_TYPE,
                        "未识别的responseType配置");
            }
        } else {
            if (StringUtils.isBlank(SecurityOauth2Manager.getConfig().getLoginPage())) {
                throw new SecurityOauth2Exception("登录页未配置,请联系管理员");
            }
            //构建redirect_url地址
            SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();
            String baseUri = StringUtils.isBlank(SecurityOauth2Manager.getConfig().getAuthorizationUri())
                    ? securityRequest.getRequestURL() : SecurityOauth2Manager.getConfig().getAuthorizationUri();
            String redirectUrl = UriComponentsBuilder.fromHttpUrl(baseUri)
                    .queryParam(SecurityOauth2ParamConstant.CLIENT_ID, authorizeModel.getSecurityOauth2Client().getClientId())
                    .queryParam(SecurityOauth2ParamConstant.RESPONSE_TYPE, authorizeModel.getSecurityOauth2Client().getResponseType())
                    .queryParam(SecurityOauth2ParamConstant.SCOPE, authorizeModel.getSecurityOauth2Client().getScope())
                    .queryParam(SecurityOauth2ParamConstant.REDIRECT_URI, authorizeModel.getSecurityOauth2Client().getRedirectUri())
                    .queryParam(SecurityOauth2ParamConstant.STATE, authorizeModel.getSecurityOauth2Client().getState()).build().toUriString();
            // 跳转页面
            String url = UriComponentsBuilder.fromHttpUrl(SecurityOauth2Manager.getConfig().getLoginPage())
                    .queryParam(SecurityOauth2ParamConstant.REDIRECT_URL, URLEncoder.encode(redirectUrl, "UTF-8")).build().toUriString();
            modelAndView.setViewName(buildRedirectUrl(url));
        }
        return modelAndView;
    }

    /**
     * 确认
     *
     * @param modelAndView ModelAndView
     * @return ModelAndView
     */
    @PostMapping(SecurityOauth2ApiConstant.CONFIRM_URL)
    public ModelAndView confirm(ModelAndView modelAndView) {
        try {
            SecurityOauth2Service securityOauth2Service = SecurityOauth2Manager.getSecurityOauth2Service();
            SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();
            String confirmType = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.CONFIRM_TYPE);
            String code = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.CODE);
            if (SecurityOauth2CommonConstant.CONFIRM_TYPE_APPROVE.equals(confirmType)) {
                String url = securityOauth2Service.buildAuthorizationCodeUri(code);
                modelAndView.setViewName(buildRedirectUrl(url));
            } else if (SecurityOauth2CommonConstant.CONFIRM_TYPE_DENY.equals(confirmType)) {
                modelAndView.setViewName(buildRedirectUrl(SecurityOauth2ApiConstant.REVOKE_URL
                        + "?" + SecurityOauth2ParamConstant.CODE + "=" + code));
            } else {
                throw new SecurityOauth2Exception("无法识别的确认类型");
            }
        } catch (SecurityBaseException e) {
            log.error("授权确认异常：{}", e.getMessage());
            modelAndView.getModel().put(SecurityOauth2ParamConstant.ERROR_MSG, e.getMessage());
            modelAndView.setViewName("error.html");
        }
        return modelAndView;
    }

    /**
     * 撤销
     *
     * @param modelAndView ModelAndView
     * @return ModelAndView
     */
    @GetMapping(SecurityOauth2ApiConstant.REVOKE_URL)
    public ModelAndView revoke(ModelAndView modelAndView) {
        SecurityOauth2Service securityOauth2Service = SecurityOauth2Manager.getSecurityOauth2Service();
        SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();
        String code = securityRequest.getParameter(SecurityOauth2ParamConstant.CODE);
        if (StringUtils.isNoneBlank(code)) {
            securityOauth2Service.revokeAuthorization(code);
        }
        if (StringUtils.isBlank(SecurityOauth2Manager.getConfig().getRevokePage())) {
            modelAndView.setViewName("athena-security-oauth2/revoke.html");
        } else {
            modelAndView.setViewName(buildRedirectUrl(SecurityOauth2Manager.getConfig().getRevokePage()));
        }
        return modelAndView;
    }

    /**
     * token认证
     *
     * @return {@link SecurityOauth2AccessToken}
     */
    @PostMapping(SecurityOauth2ApiConstant.TOKEN_URL)
    public Object token() {
        SecurityResponseWrapper responseWrapper = SecurityFactory.getSecurityResponseWrapper.get();
        try {
            SecurityOauth2Service securityOauth2Service = SecurityOauth2Manager.getSecurityOauth2Service();
            SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();
            GrantType grantType = GrantType.getByCode(securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.GRANT_TYPE));
            if (GrantType.AUTHORIZATION_CODE.equals(grantType)) {
                //授权码模式
                String code = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.CODE);
                securityOauth2Service.buildSecurityOauth2Application();
                //根据code换取token信息
                return responseWrapper.wrapper(
                        securityOauth2Service.getAccessTokenByAuthorizationCode(code), false, null);
            } else if (GrantType.PASSWORD.equals(grantType)) {
                //密码模式
                securityRequest.setAttribute(SecurityOauth2ParamConstant.RESPONSE_TYPE,
                        SecurityOauth2CommonConstant.RESPONSE_TYPE_TOKEN);
                SecurityOauth2Client securityClientModel = securityOauth2Service.buildAuthorizationModel(grantType);
                //构建登录认证
                return responseWrapper.wrapper(
                        securityOauth2Service.grantAuthorizationLogin(securityClientModel, GrantType.PASSWORD), false, null);
            } else if (GrantType.CLIENT_CREDENTIALS.equals(grantType)) {
                //客户端模式
                securityRequest.setAttribute(SecurityOauth2ParamConstant.RESPONSE_TYPE,
                        SecurityOauth2CommonConstant.RESPONSE_TYPE_TOKEN);
                SecurityOauth2Client securityClientModel = securityOauth2Service.buildAuthorizationModel(grantType);
                //构建登录认证
                return responseWrapper.wrapper(
                        securityOauth2Service.grantAuthorizationLogin(securityClientModel, GrantType.CLIENT_CREDENTIALS), false, null);
            } else {
                throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_UNRECOGNIZED_GRANT_TYPE,
                        "无效的授权类型");
            }
        } catch (SecurityBaseException exception) {
            log.error("token认证错误,异常原因：{}，错误码：{}", exception.getMessage(), exception.getCode());
            return responseWrapper.wrapper(
                    exception.getMessage(), true, exception);
        }
    }

    /**
     * 获取用户信息
     *
     * @return 用户信息
     */
    @PostMapping(SecurityOauth2ApiConstant.USERINFO_URL)
    public Object userInfo() {
        SecurityResponseWrapper responseWrapper = SecurityFactory.getSecurityResponseWrapper.get();
        SecurityRequest securityRequest = SecurityFactory.getSecurityRequest.get();
        String accessToken = securityRequest.getParameterNonNull(SecurityOauth2ParamConstant.ACCESS_TOKEN);
        final Map<String, Object> userMap = getUserInfoMap(accessToken);
        return responseWrapper.wrapper(userMap, false, null);
    }

    /**
     * 刷新token
     *
     * @return {@link SecurityOauth2RefreshToken}
     */
    @PostMapping(SecurityOauth2ApiConstant.REFRESH_URL)
    public Object refresh() {
        SecurityResponseWrapper responseWrapper = SecurityFactory.getSecurityResponseWrapper.get();
        try {
            SecurityOauth2Service securityOauth2Service = SecurityOauth2Manager.getSecurityOauth2Service();
            return responseWrapper.wrapper(securityOauth2Service.refreshToken(), false, null);
        } catch (SecurityBaseException exception) {
            log.error("刷新token错误,异常原因：{}，错误码：{}", exception.getMessage(), exception.getCode());
            return responseWrapper.wrapper(
                    exception.getMessage(), true, exception);
        }
    }

    /**
     * 获取用户信息
     *
     * @param accessToken 访问token
     * @return Map<String, String>
     */
    private Map<String, Object> getUserInfoMap(String accessToken) {
        SecurityOauth2Service securityOauth2Service = SecurityOauth2Manager.getSecurityOauth2Service();
        SecurityOauth2Details securityOauth2Details = securityOauth2Service.getUserInfo(accessToken);
        if (Objects.isNull(securityOauth2Details)) {
            throw new SecurityOauth2Exception(SecurityOauth2ErrorCodeConstant.OAUTH2_CODE_INVALID_ACCESS_TOKEN, "无效的访问token");
        }
        //校验accessToken是否失效
        SecurityUtil.securityService.get().checkToken(accessToken);
        SecurityUserDetailsService userDetailsService = SecurityFactory.getSecurityUserDetailsService.get();
        SecurityUserDetails securityUserDetails = userDetailsService.findSecurityUserDetailsByLoginId(securityOauth2Details.getLoginId());
        Map<String, Object> userMap = new HashMap<>();
        if (SecurityOauth2CommonConstant.SCOPE_ALL.equals(securityOauth2Details.getScope()) && Objects.nonNull(securityUserDetails.getDetailMap())
                && !securityUserDetails.getDetailMap().isEmpty()) {
            userMap.putAll(securityUserDetails.getDetailMap());
        }

        //ID
        userMap.put(SecurityOauth2ParamConstant.LOGIN_ID, securityOauth2Details.getLoginId());
        //登录名
        userMap.put(SecurityOauth2ParamConstant.USERNAME, securityUserDetails.getUsername());
        //姓名
        userMap.put(SecurityOauth2ParamConstant.NAME, securityUserDetails.getName());
        return userMap;
    }

    /**
     * 隐式授权模式（Implicit Grant）
     *
     * @param modelAndView   ModelAndView
     * @param authorizeModel AuthorizeModel
     */
    private void implicitGrant(ModelAndView modelAndView, AuthorizeModel authorizeModel) {
        String redirectUri = authorizeModel.getSecurityOauth2Client().getRedirectUri();
        String url = redirectUri +
                (redirectUri.contains("?") ? "&" : "?") +
                "accessToken=" + authorizeModel.getSecurityOauth2AccessToken().getAccessToken() +
                "&expiresIn=" + authorizeModel.getSecurityOauth2AccessToken().getExpiresIn() +
                "&tokenType=" + authorizeModel.getSecurityOauth2AccessToken().getTokenType() +
                "&state=" + (StringUtils.isNoneBlank(authorizeModel.getSecurityOauth2Client().getState()) ? authorizeModel.getSecurityOauth2Client().getState() : "");
        modelAndView.setViewName(buildRedirectUrl(url));
    }

    /**
     * 授权码模式（Authorization Code Grant）
     *
     * @param securityOauth2Service SecurityOauth2Service
     * @param modelAndView          ModelAndView
     */
    private void authorizationCodeGrant(SecurityOauth2Service securityOauth2Service, ModelAndView modelAndView, AuthorizeModel authorizeModel) {
        String authorizationCode = authorizeModel.getCode();
        //判断是否自动确认同意授权
        boolean autoAgreeAuthorization = SecurityOauth2Manager.getConfig().getAutoAgreeAuthorization();
        if (Objects.nonNull(authorizeModel.getSecurityOauth2Client().getAutoAgreeAuthorization())) {
            autoAgreeAuthorization = authorizeModel.getSecurityOauth2Client().getAutoAgreeAuthorization();
        }
        if (autoAgreeAuthorization) {
            String url = securityOauth2Service.buildAuthorizationCodeUri(authorizationCode);
            modelAndView.setViewName(buildRedirectUrl(url));
        } else {
            //跳转到授权确认页面
            SecurityOauth2Client securityClientModel = authorizeModel.getSecurityOauth2Client();
            String confirmPage = SecurityOauth2Manager.getConfig().getConfirmPage();
            if (StringUtils.isBlank(confirmPage)) {
                securityClientModel.setClientId(securityClientModel.getClientId());
                securityClientModel.setClientSecret(securityClientModel.getClientSecret());
                modelAndView.getModel().put("securityClientModel", securityClientModel);
                modelAndView.getModel().put(SecurityOauth2ParamConstant.CODE, authorizationCode);
                modelAndView.setViewName("athena-security-oauth2/confirm.html");
            } else {
                //自定义确认页面
                String url = confirmPage.contains("?") ? "&" : "?" +
                        SecurityOauth2ParamConstant.CLIENT_NAME + "=" + securityClientModel.getClientName() +
                        "&" + SecurityOauth2ParamConstant.SCOPE + "=" + securityClientModel.getScope() +
                        "&" + SecurityOauth2ParamConstant.CODE + "=" + authorizationCode;
                modelAndView.setViewName(buildRedirectUrl(url));
            }
        }
    }


    /**
     * 构建跳转地址
     *
     * @param url 地址
     * @return 跳转地址
     */
    private String buildRedirectUrl(String url) {
        return "redirect:" + url;
    }

}
